package com.darwin.darwinGpuEngine.core {

    import com.darwin.darwinBitmapEngine.core.INode;
    import com.darwin.darwinBitmapEngine.utils.G_ID;
    import com.darwin.darwinGpuEngine.core.geom.Face;
    import com.darwin.darwinGpuEngine.core.materials.ASpriteSheetBase;
    import com.darwin.darwinGpuEngine.core.materials.BlendModePresets;
    import com.darwin.darwinGpuEngine.core.materials.GNodeMaskMaterial;
    import com.darwin.darwinGpuEngine.core.materials.GNodeMaterial;
    import com.darwin.darwinGpuEngine.core.materials.SpriteSheet;
    import com.darwin.darwinGpuEngine.core.materials.Texture2D;
    import com.darwin.darwinGpuEngine.utils.NodeBlendMode;
    import com.darwin.darwinGpuEngine.utils.StatsObject;
    import com.darwin.darwinGpuEngine.utils.TextureHelper;
    
    import flash.display.BitmapData;
    import flash.display.Stage;
    import flash.display3D.Context3D;
    import flash.display3D.textures.Texture;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.MouseEvent;
    import flash.geom.ColorTransform;
    import flash.geom.Matrix3D;
    import flash.geom.Point;
    import flash.geom.Vector3D;

    /**
     * 节点被添加到舞台
     * @eventType flash.events.Event.ADDED_TO_STAGE
     */
    [Event(name="addedToStage", type="flash.events.Event")]

    /**
     * 节点被从舞台上移除
     * @eventType flash.events.Event.REMOVED_FROM_STAGE
     */
    [Event(name="removedFromStage", type="flash.events.Event")]

    /**
     * 节点被点击
     * @eventType flash.events.MouseEvent.CLICK
     */
    [Event(name="click", type="flash.events.MouseEvent")]

    /**
     * 在节点上按下鼠标左键
     * @eventType flash.events.MouseEvent.MOUSE_DOWN
     */
    [Event(name="mouseDown", type="flash.events.MouseEvent")]

    /**
     * 在节点上移动鼠标
     * @eventType flash.events.MouseEvent.MOUSE_MOVE
     */
    [Event(name="mouseMove", type="flash.events.MouseEvent")]

    /**
     * 在节点上放开鼠标左键
     * @eventType flash.events.MouseEvent.MOUSE_UP
     */
    [Event(name="mouseUp", type="flash.events.MouseEvent")]

    /**
     * 鼠标进入节点
     * @eventType flash.events.MouseEvent.MOUSE_OVER
     */
    [Event(name="mouseOver", type="flash.events.MouseEvent")]

    /**
     * 鼠标离开节点
     * @eventType flash.events.MouseEvent.MOUSE_OUT
     */
    [Event(name="mouseOut", type="flash.events.MouseEvent")]

    /**
     * <p>基本显示节点,所有要绘制到Stage3D的显示对象都应继承此类</p>
     * 渲染节点时会依次调用一下两个方法
     * <ul>
     * <li>step - 在此方法中更新节点的位置和坐标变换</li>
     * <li>draw - 在此方法中编写实际的渲染代码</li>
     * </ul>
	 * @author Roland
     */

    public class GNode extends EventDispatcher{

        /**
         * @private
         */
        public var localModelMatrix:Matrix3D = new Matrix3D();

        /**
         * @private
         */
        public var worldModelMatrix:Matrix3D = new Matrix3D();

        /**
         * @private
         */
        public var invalidateMatrix:Boolean = true;

        /**
         * @private
         */
        public var invalidateVisibility:Boolean = true;

        /**
         * @private
         */
        public var invalidateColors:Boolean = true;

        public var children:Vector.<GNode> = new Vector.<GNode>();
        private var _parent:GNode;

		public function get parent():GNode
		{
			return _parent;
		}

		public function set parent(value:GNode):void
		{
			_parent = value;
		}


        public var vx:Number = 0.0;
        public var vy:Number = 0.0;

        public var blendMode:NodeBlendMode = BlendModePresets.NORMAL_PREMULTIPLIED_ALPHA;

        public var mouseEnabled:Boolean = false;

        public var timeSinceStartInSeconds:Number = 0.0;

        protected var stage:Stage;

        private var localMouse:Vector3D;
        private var mouseInNode:Boolean = false;
        private var localMouseMatrix:Matrix3D = new Matrix3D();

		private var _g_ID:uint;
		public function get g_ID():uint
		{
			return _g_ID;
		}
		
        /**
         * @private
         */
        protected var _width:Number;

        public function get width():Number {
            return Math.abs(_width * _scaleX);
        }

        public function set width(value:Number):void {
            scaleX = value / _width;
        }

        /**
         * @private
         */
        protected var _height:Number;

        public function get height():Number {
            return Math.abs(_height * _scaleY);
        }

        public function set height(value:Number):void {
            scaleY = value / _height;
        }

        protected var _visible:Boolean = true;

        public function get visible():Boolean {
            return _visible;
        }

        public function set visible(value:Boolean):void {
            if(_visible != value) {
                _visible = value;
                invalidateVisibility = true;
            }
        }

        protected var _alpha:Number = 1.0;

        public function set alpha(value:Number):void {
            if(_alpha != value) {
                _alpha = value;
                invalidateColors = true;
                visible = _alpha > 0.0;
            }
        }

        public function get alpha():Number {
            return _alpha;
        }

        /**
         * @private
         */
        public var combinedColorTransform:ColorTransform = new ColorTransform();

        protected var _colorTransform:ColorTransform = new ColorTransform();

        public function get colorTransform():ColorTransform {
            return _colorTransform;
        }

        public function set colorTransform(value:ColorTransform):void {
            if(_colorTransform != value) {
                _colorTransform = value;
                invalidateColors = true;
            }
        }

        protected var _tint:Number = 0xFFFFFF;

        public function get tint():Number {
            return _tint;
        }

        public function set tint(value:Number):void {
            if(_tint != value) {
                _tint = value;

                var r:Number = (_tint >> 16) / 255.0;
                var g:Number = (_tint >> 8 & 255) / 255.0;
                var b:Number = (_tint & 255) / 255.0;

                colorTransform.redMultiplier = r;
                colorTransform.greenMultiplier = g;
                colorTransform.blueMultiplier = b;
                colorTransform.alphaMultiplier = 1.0;
                colorTransform.redOffset = 0;
                colorTransform.greenOffset = 0;
                colorTransform.blueOffset = 0;
                colorTransform.alphaOffset = 0;

                invalidateColors = true;
            }
        }

        protected var _scaleX:Number = 1.0;

        public function set scaleX(value:Number):void {
            if(_scaleX != value) {
                _scaleX = value;
                invalidateMatrix = true;
            }
        }

        public function get scaleX():Number {
            return _scaleX;
        }

        protected var _scaleY:Number = 1.0;

        public function set scaleY(value:Number):void {
            if(_scaleY != value) {
                _scaleY = value;
                invalidateMatrix = true;
            }
        }

        public function get scaleY():Number {
            return _scaleY;
        }

        protected var _x:Number = 0.0;

        public function set x(value:Number):void {
            if(_x != value) {
                _position.x = _x = value;
                invalidateMatrix = true;
            }
        }

        public function get x():Number {
            return _x;
        }

        protected var _y:Number = 0.0;

        public function set y(value:Number):void {
            if(_y != value) {
                _position.y = _y = value;
                invalidateMatrix = true;
            }
        }

        public function get y():Number {
            return _y;
        }

        protected var _position:Point = new Point(0.0, 0.0);

        public function get position():Point {
            return _position;
        }

        public function set position(value:Point):void {
            if(_x != value.x || _y != value.y) {
                _position.x = _x = value.x;
                _position.y = _y = value.y;
                invalidateMatrix = true;
            }
        }

        protected var _pivot:Point = new Point(0.0, 0.0);

        public function get pivot():Point {
            return _pivot;
        }

        public function set pivot(value:Point):void {
            if(_pivot.x != value.x || _pivot.y != value.y) {
                _pivot.x = value.x;
                _pivot.y = value.y;
                invalidateMatrix = true;
            }
        }

        protected var _rotation:Number = 0.0;

        public function set rotation(value:Number):void {
            if(_rotation != value) {
                _rotation = value;
                invalidateMatrix = true;
            }
        }

        public function get rotation():Number {
            return _rotation;
        }

        protected var _mouseX:Number = 0.0;

        public function get mouseX():Number {
            return _mouseX;
        }

        protected var _mouseY:Number = 0.0;

        public function get mouseY():Number {
            return _mouseY;
        }

        public function get numTris():uint {

            var tris:uint = 0;

            for each(var child:GNode in children){
                tris += child.numTris;
            }

            return 2+tris;//tris;
        }

        public function get drawCalls():uint {

            var calls:uint = 0;

            for each(var child:GNode in children){
                calls += child.drawCalls;
            }
			return material ? (material.drawCalls + calls) : 0;
        }

        public function get numChildren():uint {
            return children.length;
        }

		//源位图数据
		private var _bitmapData:BitmapData;

		public function get bitmapData():BitmapData
		{
			return _bitmapData;
		}

		public var texture:Texture2D;
		public var spriteSheet:ASpriteSheetBase;
		public var material:GNodeMaterial;
		protected var faceList:Vector.<Face>;
		protected var mask:GNode;
		
        public function GNode(bitmapData:BitmapData = null) {
			_bitmapData = bitmapData;
			_g_ID = G_ID.id;
			
			faceList = TextureHelper.generateQuadFromDimensions(2, 2);
			
			if(bitmapData) {
				var tex:Texture2D;
				tex = new Texture2D(bitmapData as BitmapData);
				setMaterial(new GNodeMaterial());
				setTexture(tex);
			}
        }
		
		public function setSpriteSheet(value:ASpriteSheetBase):void {
			this.spriteSheet = value;
			
			if(spriteSheet) {
				_width = spriteSheet.spriteWidth;
				_height = spriteSheet.spriteHeight;
			}
		}
		
		public function setTexture(value:Texture2D):void {
			
			if(texture) {
				texture.cleanUp();
			}
			
			this.texture = value;
			
			if(texture && !spriteSheet) {
				_width = texture.originalTextureWidth;
				_height = texture.originalTextureHeight;
			}
		}
		
		public function setMaterial(value:GNodeMaterial):void {
			if(material) {
				material.cleanUp();
			}
			this.material = value;
		}
		
		
		public function setMask(mask:GNode):void {
			
			this.mask = mask;
			
			if(mask) {
				setMaterial(new GNodeMaskMaterial());
			} else {
				setMaterial(new GNodeMaterial());
			}
		}
		
        /**
         * @private
         */
        public function updateMatrix():void {
            invalidateMatrix = false;
            localModelMatrix.identity();
            localModelMatrix.appendTranslation(-_pivot.x, -_pivot.y, 0);
            localModelMatrix.appendScale(_scaleX, _scaleY, 1.0);
            localModelMatrix.appendRotation(_rotation, Vector3D.Z_AXIS);
            localModelMatrix.appendTranslation(_x, _y, 0.0);
        }

        /**
         * @private
         */
        public function updateColors():void {

            invalidateColors = false;

            combinedColorTransform.redMultiplier = colorTransform.redMultiplier;
            combinedColorTransform.greenMultiplier = colorTransform.greenMultiplier;
            combinedColorTransform.blueMultiplier = colorTransform.blueMultiplier;
            combinedColorTransform.alphaMultiplier = colorTransform.alphaMultiplier;
            combinedColorTransform.redOffset = colorTransform.redOffset;
            combinedColorTransform.greenOffset = colorTransform.greenOffset;
            combinedColorTransform.blueOffset = colorTransform.blueOffset;
            combinedColorTransform.alphaOffset = colorTransform.alphaOffset;

            if(parent) {
                combinedColorTransform.concat(parent.combinedColorTransform);
            }

            for each(var child:GNode in children){
                child.updateColors();
            }
        }

        /**
         * @private
         */
        internal function processMouseEvents(mousePosition:Vector3D, mouseEventType:String, projectionMatrix:Matrix3D):void {

            if(mouseEnabled && mouseEventType) {
                // 变换鼠标位置到本地坐标系
                localMouseMatrix.identity();
                localMouseMatrix.append(worldModelMatrix);
                localMouseMatrix.append(projectionMatrix);
                localMouseMatrix.invert();

                localMouse = localMouseMatrix.transformVector(mousePosition);
                localMouse.w = 1.0 / localMouse.w;
                localMouse.x /= localMouse.w;
                localMouse.y /= localMouse.w;
                localMouse.z /= localMouse.w;

                _mouseX = localMouse.x;
                _mouseY = localMouse.y;

                if(!isNaN(width) && !isNaN(height)) {

                    var oldMouseInNodeState:Boolean = mouseInNode;
                    mouseInNode = (mouseX >= -_width * 0.5 && mouseX <= _width * 0.5 && mouseY >= -_height * 0.5 && mouseY <= _height * 0.5);

                    if(mouseInNode) {
                        if(!oldMouseInNodeState) {
                            dispatchMouseEvent(MouseEvent.MOUSE_OVER);
                        }
                        dispatchMouseEvent(mouseEventType);
                    } else if(oldMouseInNodeState && !mouseInNode) {
                        dispatchMouseEvent(MouseEvent.MOUSE_OUT);
                    }
                }
            }

            for each(var child:GNode in children) {
                child.processMouseEvents(mousePosition, mouseEventType, projectionMatrix);
            }
        }

        internal function setStageRef(value:Stage):void {

            if(stage != value) {

                if(value) {
                    stage = value;
                    dispatchEvent(new Event(Event.ADDED_TO_STAGE));
                } else {
                    dispatchEvent(new Event(Event.REMOVED_FROM_STAGE));
                    stage = value;
                }

                for each(var child:GNode in children) {
                    child.setStageRef(value);
                }
            }
        }

        /**
         * @private
         */
        internal function stepNode(elapsed:Number):void {

            step(elapsed);

            for each(var child:GNode in children) {
                child.timeSinceStartInSeconds = timeSinceStartInSeconds;
                child.stepNode(elapsed);
            }
			
			if(spriteSheet) {
				spriteSheet.update(timeSinceStartInSeconds);
				_width = spriteSheet.spriteWidth;
				_height = spriteSheet.spriteHeight;
			}
        }

        public function handleDeviceLoss():void {
            for each(var child:GNode in children) {
                child.handleDeviceLoss();
            }
			if(material)
				material.handleDeviceLoss();
            // 覆盖此方法来扩展功能
        }

        /**
         * @private
         */
        internal function drawNode(context:Context3D, camera:GCam, parentMatrixChanged:Boolean, statsObject:StatsObject):void {

            var myMatrixChanged:Boolean = false;

            if(!_visible) {
                return;
            }

            if(invalidateColors) {
                updateColors();
            }

            if(invalidateMatrix) {
                updateMatrix();
                myMatrixChanged = true;
            }

            if(parentMatrixChanged || myMatrixChanged) {
                worldModelMatrix.identity();
                worldModelMatrix.append(localModelMatrix);

                if(parent) {
                    worldModelMatrix.append(parent.worldModelMatrix);
                }
            }

            draw(context, camera);
			statsObject.totalDrawCalls += drawCalls;
			statsObject.totalTris += numTris;
			
            for each(var child:GNode in children) {
                child.drawNode(context, camera, myMatrixChanged, statsObject);
            }
        }

        private function dispatchMouseEvent(mouseEventType:String):void {
            dispatchEvent(new MouseEvent(mouseEventType, true, false, localMouse.x, localMouse.y, null, false, false, false,
                                         (mouseEventType == MouseEvent.MOUSE_DOWN), 0));
        }

        protected function draw(context:Context3D, camera:GCam):void {
			
			material.blendMode = blendMode;
			material.modelMatrix = worldModelMatrix;
			material.projectionMatrix = camera.projectionMatrix;
			material.viewProjectionMatrix = camera.getViewProjectionMatrix();
			material.colorTransform = combinedColorTransform;
			material.spriteSheet = spriteSheet;
			material.texture = texture;
			
			if(mask) {
				
				if(mask.invalidateMatrix) {
					mask.updateMatrix();
				}
				
				var maskMat:GNodeMaskMaterial = GNodeMaskMaterial(material);
				maskMat.maskTexture = mask.texture;
				maskMat.maskModelMatrix = mask.localModelMatrix;
				maskMat.maskAlpha = mask.alpha;
			}
			
			material.render(context, faceList, 0, faceList.length);
			// 在继承GNode的类中实现此方法
        }

        protected function step(elapsed:Number):void {
			// 在继承GNode的类中实现此方法
        }

		/**
		 *	此方法在GPU引擎中不适用，请使用step和draw方法来处理渲染过程
		 */
		public function render():void
		{
			throw new Error("gpu引擎中请使用step和draw方法来处理渲染过程");
		}
		
        public function addChild(child:GNode):GNode {
            return addChildAt(child, children.length);
        }

        public function addChildAt(child:GNode, idx:uint):GNode {

            var existingIdx:int = getChildIndex(child);
            if(existingIdx != -1) {
                removeChildAt(existingIdx);
            }

            child.parent = this;
            child.setStageRef(stage);
            children.splice(idx, 0, child);
            return child;
        }

        public function removeChild(child:GNode):void {

            var idx:int = children.indexOf(child);

            if(idx >= 0) {
                removeChildAt(idx);
            }
        }

        public function removeChildAt(idx:uint):void {
            if(idx < children.length) {
                children[idx].parent = null;
                children[idx].setStageRef(null);
                children.splice(idx, 1);
            }
        }

        public function getChildAt(idx:uint):GNode {
            if(idx < children.length) {
                return children[idx];
            }

            return null;
        }

        public function getChildIndex(child:GNode):int {
            return children.indexOf(child);
        }

        public function swapChildren(child1:GNode, child2:GNode):void {
            var idx1:uint = getChildIndex(child1);
            var idx2:uint = getChildIndex(child2);
            children[idx1] = child2;
            children[idx2] = child1;
        }

        public function removeAllChildren():void {
            while(children.length > 0) {
                removeChildAt(0);
            }
        }

        public function cleanUp():void {
			if(material) {
				material.cleanUp();
				material = null;
			}
			
			if(texture) {
				texture.cleanUp();
				texture = null;
			}

            for each(var child:GNode in children) {
                child.cleanUp();
            }
        }
    }
}